/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2022-2022. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 and
 * only version 2 as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * Description:
 * Author: huawei
 * Create: 2022-11-29
 */
#include <linux/slab.h>
#include <linux/list.h>
#include <linux/rwsem.h>
#include <linux/kref.h>
#include <linux/rbtree.h>

#include "devmm_proc_info.h"
#include "svm_master_proc_mng.h"
#include "svm_master_convert.h"
#include "svm_master_dma_desc_mng.h"
#include "svm_task_dev_res_mng.h"

struct devmm_task_dev_res_node *devmm_task_dev_res_node_create(struct devmm_svm_process *svm_proc,
    struct svm_id_inst *id_inst)
{
    struct devmm_svm_proc_master *master_data = (struct devmm_svm_proc_master *)svm_proc->priv_data;
    struct devmm_task_dev_res_info *info = &master_data->task_dev_res_info;
    struct devmm_task_dev_res_node *node = NULL;

    node = kzalloc(sizeof(struct devmm_task_dev_res_node), GFP_KERNEL | __GFP_ACCOUNT);
    if (node == NULL) {
        devmm_drv_err("Kzalloc failed.\n");
        return NULL;
    }

    node->dev_res_mng = devmm_dev_res_mng_get(id_inst);
    if (node->dev_res_mng == NULL) {
        devmm_drv_err("Get dev res mng failed. (devid=%u; vfid=%u)\n", id_inst->devid, id_inst->vfid);
        kfree(node);
        return NULL;
    }

    node->svm_proc = svm_proc;  /* node->svm_proc's access can ensure int task context, so not get ref */
    node->host_pid = svm_proc->process_id.hostpid;
    node->id_inst = *id_inst;
    node->vm_id = svm_proc->process_id.vm_id;

    INIT_LIST_HEAD(&node->task_node);
    INIT_LIST_HEAD(&node->dev_res_node);
    kref_init(&node->ref);

    node->convert_rb_info.root = RB_ROOT;
    init_rwsem(&node->convert_rb_info.rw_sem);

    down_write(&info->rw_sem);
    list_add(&node->task_node, &info->head);
    up_write(&info->rw_sem);

    down_write(&node->dev_res_mng->task_dev_res_info.rw_sem);
    list_add(&node->dev_res_node, &node->dev_res_mng->task_dev_res_info.head);
    up_write(&node->dev_res_mng->task_dev_res_info.rw_sem);
    return node;
}

static void devmm_task_dev_res_node_release(struct kref *kref)
{
    struct devmm_task_dev_res_node *node = container_of(kref, struct devmm_task_dev_res_node, ref);

    kfree(node);
}

static void devmm_task_dev_res_node_subres_recycle(struct devmm_task_dev_res_node *node)
{
    u32 convert_node_num = 0;

    devmm_convert_nodes_destroy_by_task_release(node, &convert_node_num);

    devmm_drv_debug("Proc release destroy info. (host_pid=%d; convert_node_num=%u)\n",
        node->host_pid, convert_node_num);
}

void devmm_task_dev_res_node_destroy(struct devmm_task_dev_res_node *node)
{
    struct devmm_svm_proc_master *master_data = (struct devmm_svm_proc_master *)node->svm_proc->priv_data;
    struct devmm_task_dev_res_info *proc_info = &master_data->task_dev_res_info;

    devmm_task_dev_res_node_subres_recycle(node);

    down_write(&proc_info->rw_sem);
    list_del_init(&node->task_node);
    up_write(&proc_info->rw_sem);

    down_write(&node->dev_res_mng->task_dev_res_info.rw_sem);
    list_del_init(&node->dev_res_node);
    up_write(&node->dev_res_mng->task_dev_res_info.rw_sem);

    devmm_dev_res_mng_put(node->dev_res_mng);
    kref_put(&node->ref, devmm_task_dev_res_node_release);
}

void devmm_task_dev_res_node_get(struct devmm_task_dev_res_node *node)
{
    kref_get(&node->ref);
}

void devmm_task_dev_res_node_put(struct devmm_task_dev_res_node *node)
{
    kref_put(&node->ref, devmm_task_dev_res_node_release);
}

struct devmm_task_dev_res_node *devmm_task_dev_res_node_get_by_task(
    struct devmm_svm_process *svm_proc, struct svm_id_inst *id_inst)
{
    struct devmm_svm_proc_master *master_data = (struct devmm_svm_proc_master *)svm_proc->priv_data;
    struct devmm_task_dev_res_info *info = &master_data->task_dev_res_info;
    struct devmm_task_dev_res_node *tmp = NULL;
    struct list_head *head = &info->head;
    struct list_head *n = NULL;
    struct list_head *pos = NULL;

    down_read(&info->rw_sem);
    list_for_each_safe(pos, n, head) {
        tmp = list_entry(pos, struct devmm_task_dev_res_node, task_node);
        if ((tmp->id_inst.devid == id_inst->devid) && (tmp->id_inst.vfid == id_inst->vfid)) {
            devmm_task_dev_res_node_get(tmp);
            up_read(&info->rw_sem);
            return tmp;
        }
    }
    up_read(&info->rw_sem);
    return NULL;
}

static struct devmm_task_dev_res_node *devmm_del_one_task_dev_res_node(struct devmm_task_dev_res_info *info)
{
    struct devmm_task_dev_res_node *node = NULL;

    down_write(&info->rw_sem);
    if (list_empty(&info->head) != 0) {
        up_write(&info->rw_sem);
        return NULL;
    }

    node = list_last_entry(&info->head, struct devmm_task_dev_res_node, task_node);
    list_del_init(&node->task_node);
    devmm_task_dev_res_node_get(node);  /* get node ref in lock, or the node will be release */
    up_write(&info->rw_sem);
    return node;
}

void devmm_task_dev_res_nodes_destroy_by_task(struct devmm_svm_process *svm_proc)
{
    struct devmm_svm_proc_master *master_data = (struct devmm_svm_proc_master *)svm_proc->priv_data;
    struct devmm_task_dev_res_info *info = &master_data->task_dev_res_info;
    struct devmm_task_dev_res_node *node = NULL;
    u32 stamp = (u32)jiffies;

    while (1) {
        node = devmm_del_one_task_dev_res_node(info);
        if (node == NULL) {
            break;
        }
        devmm_task_dev_res_node_destroy(node);
        devmm_task_dev_res_node_put(node);
        devmm_try_cond_resched(&stamp);
    }
}
